﻿using System;
using System.Data;
using MySql.Data.MySqlClient;
using MySql.Data.Types;
using System.Threading;
using snat.EventFramework.Errors;
using snat.loggers;

namespace snat.proxy
{
    public sealed class DataProxy
    {

        private static volatile DataProxy instance = new DataProxy();

        private MySqlConnection[] connections;
        private object[] connectionLocks;
        private Semaphore connectionSem;

        public event EventHandler<ErrorArgs> onError;

        private void Error(object source, ErrorArgs arguments) {
            onError += snat.loggers.ErrorLogger.Instance.Listener;
            if (onError != null) {
                onError(source, arguments);
            }
            else { //todo remove print out when code is complete. Here for debug only
                Console.WriteLine("Unhandled event occurred. Should this happen?");
            }
        }


        private DataProxy() {
            int connectionCount = 2;
            connections = new MySqlConnection[connectionCount];
            connectionLocks = new object[connectionCount];
            connectionSem = new Semaphore(connectionCount, connectionCount);

            //test the logger - todo remove
            Error(this, new ErrorArgs("Events test", "This is some clean clean code", severity.Informational));
        }



        public static DataProxy Instance {
            get {
                return instance;
            }
        }

        /// <summary>
        /// Finalization method for the DataProxy
        /// <remarks>
        /// The proxyLock and queryLocks are locked in case a slow query is still in the tubes.
        /// Shouldn't happen, but safety first.
        /// </remarks>
        /// </summary>
        ~DataProxy() {
            //ensure all connections not active and can not be used
            for (int finished = 0; finished < connections.Length; finished++) connectionSem.WaitOne();
            //close all connections
            foreach (MySqlConnection connection in connections) connection.Close();
        }



        public void connect(string serverURI, int port, string databaseName, string userName, string password) {
            int failiures = 0;
            for (int i = 0; i < connections.Length; i++) {
                connectionLocks[i] = new object();
                try {
                    connections[i] = new MySqlConnection(
                        String.Join(String.Empty,
                                    "server=", serverURI,
                                    ";port=",port,
                                    ";database=", databaseName,
                                    ";uid=", userName,
                                    ";password=", password,
                                    ";Persist Security Info=False;"));
                    connections[i].Open();
                }
                catch (MySqlException ex) {
                    lock (connectionLocks[i])  //prevent any access to failed connection
                    Error(connections[i], new ErrorArgs("Connection failed to open",
                       "Connection " + i + "failed, server issue? ", severity.Major, ex));
                    failiures++;

                    if (failiures == connections.Length) {
                        Error(this, new ErrorArgs("Failed to connect to the database", "All connections failed",
                            severity.Severe));
                        throw ex;
                    }
                }
            }
        }

        public DataSet RunQuery(MySqlCommand sqlQuery, int position, int bufferSize) {
         connectionSem.WaitOne(); //ensure never more threads than connectors
            //todo check if connected and reconnect
            DataSet dataSet = new DataSet();

            int i = 0;

            while (!Monitor.TryEnter(connectionLocks[i])) {
                i += 1;
                i %= connectionLocks.Length; //should never wrap round, but defensive code
            }

            try {
                MySqlDataAdapter adapter = new MySqlDataAdapter();
                MySqlCommand command = sqlQuery.Clone();

                command.Connection = connections[i];

                command.Parameters.AddWithValue("position", position);
                command.Parameters.AddWithValue("bufferSize", bufferSize);
                command.Prepare();

                adapter.SelectCommand = command;
                adapter.Fill(dataSet);
            }
            catch (MySqlConversionException ex) {
                Error(ex.Source, new ErrorArgs("MySqlConversion Exception in Dataproxy", ex.Message + "\nThe query ran was:\n\t" + sqlQuery, severity.Major, ex));
                throw ex;
            }


            //no need to pulse, never more than one waiting
            Monitor.Exit(connectionLocks[i]);


            connectionSem.Release();
            return dataSet;

        }


        public DataSet RunQuery(MySqlCommand sqlQuery) {
            connectionSem.WaitOne(); //ensure never more threads than connectors
            //todo check if connected and reconnect
            DataSet dataSet = new DataSet();

            int i = 0;

            while (!Monitor.TryEnter(connectionLocks[i])) {
                i += 1;
                i %= connectionLocks.Length; //should never wrap round, but defensive code
            }

            try {
                MySqlDataAdapter adapter = new MySqlDataAdapter();
                MySqlCommand command = sqlQuery.Clone();
                command.Connection = connections[i];
                adapter.SelectCommand = command;
                adapter.Fill(dataSet);
            }
            catch (MySqlConversionException ex) {
                Error(ex.Source, new ErrorArgs("MySqlConversion Exception in Dataproxy", ex.Message + "\nThe query ran was:\n\t" + sqlQuery, severity.Major, ex));
                throw ex;
            }


            //no need to pulse, never more than one waiting
            Monitor.Exit(connectionLocks[i]);


            connectionSem.Release();
            return dataSet;
        
        
        }

        public DataSet RunQuery(string sqlQuery) {
            connectionSem.WaitOne(); //ensure never more threads than connectors
            //todo check if connected and reconnect
            DataSet dataSet = new DataSet();

            int i = 0;

            while (!Monitor.TryEnter(connectionLocks[i])) {
                i += 1;
                i %= connectionLocks.Length; //should never wrap round, but defensive code
            }

            try {
                MySqlDataAdapter adapter = new MySqlDataAdapter();
                adapter.SelectCommand = new MySqlCommand(sqlQuery, connections[i]);
                adapter.Fill(dataSet);
            }
            catch (MySqlConversionException ex) {
                Error(ex.Source, new ErrorArgs("MySqlConversion Exception in Dataproxy", ex.Message + "\nThe query ran was:\n\t" + sqlQuery, severity.Major, ex));
                throw ex;
            }


            //no need to pulse, never more than one waiting
            Monitor.Exit(connectionLocks[i]);


            connectionSem.Release();
            return dataSet;
        }




    }
}
